"""输入清理和验证模块。"""

import re
import html
import urllib.parse
from typing import Any, Dict, List, Optional, Union
import logging

logger = logging.getLogger(__name__)


class InputSanitizer:
    """输入清理器 - 防止XSS和注入攻击。"""
    
    def __init__(self):
        """初始化清理器。"""
        # XSS危险标签
        self.dangerous_tags = [
            'script', 'iframe', 'object', 'embed', 'form', 'input', 
            'button', 'textarea', 'select', 'option', 'link', 'meta',
            'style', 'base', 'frame', 'frameset', 'applet'
        ]
        
        # 危险属性
        self.dangerous_attrs = [
            'onclick', 'onload', 'onmouseover', 'onmouseout', 'onfocus',
            'onblur', 'onchange', 'onsubmit', 'onreset', 'onselect',
            'onkeydown', 'onkeyup', 'onkeypress', 'javascript:', 'vbscript:',
            'data:', 'expression(', 'eval(', 'document.', 'window.'
        ]
        
        # SQL注入关键词
        self.sql_injection_patterns = [
            r'\b(union|select|insert|update|delete|drop|create|alter|exec|execute)\b',
            r'(\'\s*(or|and)\s*\')|(\'\s*(or|and)\s*\d)',
            r'(--|\#|\/\*|\*\/)',
            r'(\bor\b|\band\b)\s+\d+\s*=\s*\d+',
            r'\'.*(\bor\b|\band\b).*\'',
        ]
        
        # NoSQL注入模式
        self.nosql_injection_patterns = [
            r'\$where',
            r'\$regex',
            r'\$ne', 
            r'\$gt',
            r'\$lt',
            r'\$in',
            r'\$nin',
            r'\.find\(',
            r'\.remove\(',
            r'\.drop\(',
        ]
        
    def sanitize_html(self, text: str) -> str:
        """清理HTML内容防止XSS。
        
        Args:
            text: 原始HTML文本
            
        Returns:
            清理后的安全文本
        """
        if not text or not isinstance(text, str):
            return ""
            
        try:
            # 1. 先解码HTML实体
            text = html.unescape(text)
            
            # 2. 移除危险标签
            for tag in self.dangerous_tags:
                # 移除开始和结束标签
                text = re.sub(f'<{tag}[^>]*>', '', text, flags=re.IGNORECASE)
                text = re.sub(f'</{tag}>', '', text, flags=re.IGNORECASE)
                
            # 3. 移除危险属性
            for attr in self.dangerous_attrs:
                text = re.sub(f'{attr}[^>]*', '', text, flags=re.IGNORECASE)
                
            # 4. 移除javascript: 和 data: URL
            text = re.sub(r'javascript:', '', text, flags=re.IGNORECASE)
            text = re.sub(r'data:', '', text, flags=re.IGNORECASE)
            text = re.sub(r'vbscript:', '', text, flags=re.IGNORECASE)
            
            # 5. 移除style属性中的expression
            text = re.sub(r'expression\s*\(.*?\)', '', text, flags=re.IGNORECASE)
            
            # 6. 再次HTML编码特殊字符
            text = html.escape(text, quote=False)
            
            # 7. 移除多余空白
            text = re.sub(r'\s+', ' ', text).strip()
            
            return text
            
        except Exception as e:
            logger.error(f"HTML清理失败: {e}")
            return ""
    
    def detect_sql_injection(self, text: str) -> bool:
        """检测SQL注入攻击。
        
        Args:
            text: 输入文本
            
        Returns:
            是否检测到SQL注入
        """
        if not text or not isinstance(text, str):
            return False
            
        text_lower = text.lower()
        
        for pattern in self.sql_injection_patterns:
            if re.search(pattern, text_lower, re.IGNORECASE):
                logger.warning(f"检测到SQL注入模式: {pattern}")
                return True
                
        return False
    
    def detect_nosql_injection(self, data: Union[str, dict]) -> bool:
        """检测NoSQL注入攻击。
        
        Args:
            data: 输入数据（字符串或字典）
            
        Returns:
            是否检测到NoSQL注入
        """
        if isinstance(data, dict):
            # 检查字典键和值
            for key, value in data.items():
                if self.detect_nosql_injection(key) or self.detect_nosql_injection(value):
                    return True
            return False
            
        if not isinstance(data, str):
            data = str(data)
            
        data_str = data.lower()
        
        for pattern in self.nosql_injection_patterns:
            if re.search(pattern, data_str, re.IGNORECASE):
                logger.warning(f"检测到NoSQL注入模式: {pattern}")
                return True
                
        return False
    
    def sanitize_url(self, url: str) -> str:
        """清理URL防止开放重定向。
        
        Args:
            url: 原始URL
            
        Returns:
            清理后的安全URL
        """
        if not url or not isinstance(url, str):
            return ""
            
        try:
            # 解析URL
            parsed = urllib.parse.urlparse(url)
            
            # 检查协议
            if parsed.scheme not in ['http', 'https', '']:
                logger.warning(f"危险协议: {parsed.scheme}")
                return ""
                
            # 移除javascript:等危险协议
            if any(dangerous in url.lower() for dangerous in ['javascript:', 'data:', 'vbscript:']):
                return ""
                
            # 重新构建安全的URL
            safe_url = urllib.parse.urlunparse((
                parsed.scheme,
                parsed.netloc,
                parsed.path,
                parsed.params,
                parsed.query,
                ''  # 移除fragment以防止某些攻击
            ))
            
            return safe_url
            
        except Exception as e:
            logger.error(f"URL清理失败: {e}")
            return ""
    
    def validate_data_item(self, item: Dict[str, Any]) -> bool:
        """验证数据项安全性。
        
        Args:
            item: 数据项
            
        Returns:
            是否安全
        """
        try:
            # 检查是否包含注入攻击
            if self.detect_nosql_injection(item):
                logger.warning("数据项包含NoSQL注入模式")
                return False
                
            # 检查字符串字段
            for key, value in item.items():
                if isinstance(value, str):
                    if self.detect_sql_injection(value):
                        logger.warning(f"字段 {key} 包含SQL注入模式")
                        return False
                        
                elif isinstance(value, dict):
                    if not self.validate_data_item(value):
                        return False
                        
            return True
            
        except Exception as e:
            logger.error(f"数据验证失败: {e}")
            return False
    
    def sanitize_data_item(self, item: Dict[str, Any]) -> Dict[str, Any]:
        """清理数据项。
        
        Args:
            item: 原始数据项
            
        Returns:
            清理后的数据项
        """
        try:
            sanitized_item = {}
            
            for key, value in item.items():
                # 清理键名
                clean_key = self.sanitize_string(key)
                
                if isinstance(value, str):
                    # 清理字符串值
                    sanitized_item[clean_key] = self.sanitize_html(value)
                elif isinstance(value, dict):
                    # 递归清理嵌套字典
                    sanitized_item[clean_key] = self.sanitize_data_item(value)
                elif isinstance(value, list):
                    # 清理列表
                    sanitized_list = []
                    for list_item in value:
                        if isinstance(list_item, str):
                            sanitized_list.append(self.sanitize_html(list_item))
                        elif isinstance(list_item, dict):
                            sanitized_list.append(self.sanitize_data_item(list_item))
                        else:
                            sanitized_list.append(list_item)
                    sanitized_item[clean_key] = sanitized_list
                else:
                    # 其他类型直接保留
                    sanitized_item[clean_key] = value
                    
            return sanitized_item
            
        except Exception as e:
            logger.error(f"数据清理失败: {e}")
            return {}
    
    def sanitize_string(self, text: str) -> str:
        """清理普通字符串。
        
        Args:
            text: 原始字符串
            
        Returns:
            清理后的字符串
        """
        if not text or not isinstance(text, str):
            return ""
            
        # 移除控制字符
        text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)
        
        # 限制长度防止过长输入
        if len(text) > 10000:  # 限制10K字符
            text = text[:10000]
            logger.warning("字符串被截断：超过长度限制")
            
        return text.strip()


# 全局清理器实例
input_sanitizer = InputSanitizer()